
#include "channel.h"

#include "shm_common.h"
#include <sys/shm.h>

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <limits.h>


// alternative channel class
struct alternative_t
{
	key_t key;
	int id;

	pthread_mutex_t mutex;
	pthread_cond_t cond;

	int n;
};
struct alt_channel_t
{
	struct channel_t *ch;
	int priority;
	bool cond;
};


// common functions
inline int __LClockAlt( struct alternative_t *alt ) {
	return pthread_mutex_lock( & alt->mutex );
}
inline int __LCunlockAlt( struct alternative_t *alt ) {
	return pthread_mutex_unlock( & alt->mutex );
}
inline int __LCwaitOnAlt( struct alternative_t *alt ) {
	return pthread_cond_wait( & alt->cond, & alt->mutex );
}
inline int __LCwakeUpAlt( struct alternative_t *alt ) {
	return pthread_cond_signal( & alt->cond );
}

void __LCwakeUpAlternative( key_t altKey )
{
	struct alternative_t *alt;

	int id = shmget( altKey, sizeof(*alt), 0700 );
	if( id == -1 ) {
		printf( "[ALT] wakeup::shmget(): %s\n", strerror(errno) );
		return ;
	}

	alt = shmat( id, 0, 0 );
	if( ! alt ) {
		printf( "[ALT] wakeup::shmat(): %s\n", strerror(errno) );
		return ;
	}

	__LClockAlt( alt );
	__LCwakeUpAlt( alt );
	__LCunlockAlt( alt );

	shmdt( alt );
}
// end common functions


// returns the address of the i-th alt_channel of alternative alt
#define ALTCH(alt,i) ( (struct alt_channel_t*) ( (char*) alt + sizeof(*alt) + sizeof(struct alt_channel_t)*i ) )


struct alternative_t *LCcreate_alt( key_t key, int n )
{
	struct alternative_t *alt;

	int id = shmget( key, sizeof(*alt) + sizeof(struct alt_channel_t)*(n), IPC_CREAT | 0700 );
	if( id == -1 ) {
		printf( "receiver::shmget(): %s\n", strerror(errno) );
		return NULL;
	}

	alt = shmat( id, 0, 0 );
	if( alt == (void*) -1 ) {
		printf( "receiver::shmat(): %s\n", strerror(errno) );
		return NULL;
	}

	// setting alternative key and id
	alt->key = key;
	alt->id = id;

	// initing mutex and condition variable
	// (it's mandatory to set them as shared objects!)
	mutex_init( & alt->mutex );
	cond_init( & alt->cond );

	// setting other fields of the channel
	alt->n = n;

	int i;
	for( i = 0; i < n; i ++ ) {
		struct alt_channel_t *altch = ALTCH(alt,i);
		altch->cond = 0;
		altch->ch = 0;
		altch->priority = 0;
	}

	return alt;
}

void LCsetAlt( struct alternative_t *alt, int id, struct channel_t *ch, int priority, bool cond )
{
	struct alt_channel_t *altch = ALTCH(alt,id);
	altch->ch = ch;
	altch->priority = priority;
	altch->cond = cond;
}


// returns the best alternative channel id to read from
// -1 if no channel is ready yet
// -2 if no channel is ever going to be ready (all conds are set to false)
static int __getBestChId( struct alternative_t *alt, key_t altKey )
{
	int bestId = -2;
	int max = INT_MIN;

	int i;
	for( i = 0; i < alt->n; i ++ ) {
		struct alt_channel_t *altch = ALTCH(alt,i);
		struct channel_t *ch = altch->ch;

		// not polling ch
		if( ! altch->cond ) {
			continue;
		}
		// polling ch
		if( ch ) {
			// locking it and setting altKey
			__LClockChannel( ch );

			__LCsetAltKey( ch, altKey );

			// ch isn't ready yet
			if( ! LCreadyToReceive(ch) ) {
				if( bestId == -2 ) {
					bestId = -1;
				}
				__LCunlockChannel( ch );
				continue;
			}

			__LCunlockChannel( ch );
		}

		// a comodity macro
		#define SETBEST { bestId=i; max=altch->priority; }

		if( bestId < 0 ) {
			// no best id yet: this is the best one so far
			SETBEST;
			continue;
		}
		if( altch->priority > max ) {
			// ch has highest priority so far
			SETBEST;
			continue;
		}

		#undef SETBEST
	}

	return bestId;
}

// -1 if no channel is ready
// channel id (relatively to alt) of the ready one
// ch will point to the channel
int LCpoll( struct alternative_t *alt, struct channel_t **ch )
{
	int altchId;
	int i;

	__LClockAlt( alt );

	altchId = __getBestChId( alt, alt->key );
	while( altchId == -1 ) {
		// while no channel is ready... waiting
		__LCwaitOnAlt( alt );
		altchId = __getBestChId( alt, alt->key );
	}

	// resetting channels' alt key
	for( i = 0; i < alt->n; i ++ ) {
		struct channel_t *ch = ALTCH(alt, i)->ch;

		__LClockChannel( ch );
		__LCsetAltKey( ch, 0 );
		__LCunlockChannel( ch );
	}

	__LCunlockAlt( alt );

	// return values
	if( altchId == -2 ) {
		*ch = 0;
		return -1;
	}
	*ch = ALTCH(alt,altchId)->ch;
	return altchId;
}



/*
come funge:
LCpoll:
 * controlla che non ci siano canali pronti. se ci sono torna il migliore.
 * setta tutti i canali in alt-receive:
   * il campo altKey dei canali viene settato
 * si pone in attesa condizionale
 * ricerca il canale pronto migliore e lo ritorna

LCsend:
 * si comporta normalmente
 * se poi altKey e' settato:
   * alloca l'alternative su cui e' eseguita l'alt-receive (campo altKey)
   * ed esegue anche li' la send
*/

